#!/usr/bin/python
# -*- coding: utf-8 -*-
# @Time    : 15/7/2025 21:21
# @Author  : H3C Comware AI group
# @function:


import os
import grpc
import tempfile
import shutil
import uuid
import subprocess
import requests
import json

from typing import Tuple
from threading import Lock
from grpc import ServicerContext
from proto import hi_pb2 as pb2


class SVNManager:
    """处理所有SVN操作，使用原生svn命令行工具确保线程安全"""

    def __init__(self, base_svn_url: str = "http://10.153.3.138/comware-test/50.多环境移植/AIGC_aixcoder",
                 username: str = "w14512", password: str = "Wyq qwaszx!@"):
        self.base_svn_url = base_svn_url  # 基础SVN URL（后续将追加唯一路径）
        self.username = username
        self.password = password
        self.lock = Lock()  # 用于线程安全的SVN操作

    def _run_svn_command(self, command: list, cwd: str = None) -> Tuple[bool, str]:
        """执行SVN命令并返回结果"""
        try:
            # 设置认证参数
            auth_args = [
                '--non-interactive',
                '--no-auth-cache',
                f'--username={self.username}',
                f'--password={self.password}'
            ]

            # 构建完整命令
            full_cmd = ['svn'] + command + auth_args

            # 执行命令
            result = subprocess.run(
                full_cmd,
                cwd=cwd,
                capture_output=True,
                text=True,
                check=True
            )
            return True, result.stdout.strip()
        except subprocess.CalledProcessError as e:
            return False, f"SVN command failed: {e.stderr.strip() if e.stderr else str(e)}"
        except Exception as e:
            return False, f"Unexpected error: {str(e)}"

    def prepare_workspace(self) -> Tuple[str, str]:
        """
        在SVN服务器上创建唯一文件夹并检出到本地
        返回: (本地路径, SVN URL)
        """
        unique_id = uuid.uuid4().hex
        local_path = os.path.join(tempfile.gettempdir(), f"test_run_{unique_id}")
        svn_url = f"{self.base_svn_url}/{unique_id}"

        with self.lock:
            try:
                # 1. 在SVN服务器上创建目录
                success, message = self._run_svn_command([
                    'mkdir',
                    svn_url,
                    '-m', f'"Create test workspace {unique_id}"'
                ])

                if not success:
                    raise RuntimeError(f"SVN mkdir failed: {message}")

                # 2. 创建本地目录
                os.makedirs(local_path, exist_ok=True)

                # 3. 检出到本地
                success, message = self._run_svn_command([
                    'checkout',
                    svn_url,
                    local_path
                ])

                if not success:
                    raise RuntimeError(f"SVN checkout failed: {message}")

                return local_path, svn_url
            except Exception as e:
                # 清理已创建的本地目录
                shutil.rmtree(local_path, ignore_errors=True)
                # 尝试清理SVN服务器上的目录
                try:
                    self._run_svn_command([
                        'delete',
                        svn_url,
                        '-m', '"Cleanup failed creation"'
                    ])
                except:
                    pass
                raise RuntimeError(f"Workspace preparation failed: {str(e)}")

    def cleanup_workspace(self, local_path: str, svn_url: str):
        """
        删除SVN服务器上的文件夹
        Args:
            svn_url: 要删除的SVN服务器目录URL
        """
        with self.lock:
            try:
                # 1. 从SVN服务器删除目录
                self._run_svn_command([
                    'delete',
                    svn_url,
                    '-m', '"Cleanup test workspace"'
                ])

                # 2. 获取本地路径（假设与SVN URL对应）并清理
                # local_path = os.path.join(
                #     tempfile.gettempdir(),
                #     f"test_run_{os.path.basename(svn_url)}"
                # )
                if os.path.exists(local_path):
                    shutil.rmtree(local_path)
            except Exception as e:
                if 'non-existent' not in str(e) and 'E160013' not in str(e):
                    raise RuntimeError(f"Cleanup failed: {str(e)}")

    def commit_files(self, local_path: str, message: str = "Auto commit by test runner") -> Tuple[bool, str]:
        """
        提交文件到SVN服务器
        Args:
            local_path: 本地工作目录路径
            message: 提交日志信息
        Returns:
            Tuple[成功状态, 错误信息/成功信息]
        """
        if not os.path.exists(local_path):
            return False, f"Local path does not exist: {local_path}"

        try:
            with self.lock:
                # 1. 添加所有新文件和目录
                success, add_result = self._run_svn_command([
                    'add',
                    '--force',
                    '.'
                ], cwd=local_path)

                if not success:
                    return False, add_result

                # 2. 检查是否有需要提交的更改
                success, status_result = self._run_svn_command([
                    'status'
                ], cwd=local_path)

                if not success:
                    return False, status_result

                # 如果没有修改则跳过提交
                if not status_result.strip():
                    return True, "No changes to commit"

                # 3. 执行提交
                success, commit_result = self._run_svn_command([
                    'commit',
                    '-m', f'"{message}"'
                ], cwd=local_path)

                if not success:
                    return False, commit_result

                # 提取提交版本号
                if 'Committed revision' in commit_result:
                    revision = commit_result.split('Committed revision')[1].split('.')[0].strip()
                    return True, f"Committed revision {revision}"
                return True, "Commit successful"
        except Exception as e:
            return False, f"Commit failed: {str(e)}"


class ScriptValidator:
    """脚本验证&修复接口客户端"""
    
    def __init__(self, base_url: str = "http://10.114.138.76:8000"):
        self.base_url = base_url
        self.headers = {
            "Accept": "application/json, text/plain, */*",
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "zh-CN",
            "Connection": "keep-alive",
            "Content-Type": "application/json",
            "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) RDTestClient/0.6.2025042918 Chrome/108.0.5359.215 Electron/22.3.6 Safari/537.36",
        }
    
    def validate_and_repair_scripts(self, scripts_path: str) -> Tuple[bool, str, dict]:
        """
        调用脚本验证&修复接口
        Args:
            scripts_path: 脚本目录路径（SVN URL）
        Returns:
            Tuple[是否成功, 状态信息, 完整响应数据]
        """
        url = f"{self.base_url}/aigc/promote_scripts"
        
        try:
            response = requests.post(
                url,
                headers=self.headers,
                json={"scriptspath": scripts_path},
                timeout=2000
            )
            response.raise_for_status()
            response_data = response.json()
            
            return_code = response_data.get('return_code')
            return_info = response_data.get('return_info')
            
            # 根据返回码判断成功状态
            if return_code == "200":
                if "所有脚本验证&修复成功" in return_info:
                    return True, return_info, response_data
                else:
                    # 脚本验证失败，但HTTP请求成功
                    return False, return_info, response_data
            else:
                # 其他错误码
                return False, return_info, response_data
                
        except requests.exceptions.RequestException as e:
            error_msg = f"脚本验证请求失败: {str(e)}"
            if hasattr(e, 'response') and e.response:
                error_msg += f" 错误详情: {e.response.text}"
            return False, error_msg, {}
        except Exception as e:
            return False, f"脚本验证异常: {str(e)}", {}


class TestRunner:
    """具有独立SVN工作区的线程安全测试运行器"""
    svn_manager = SVNManager()

    def __init__(self, svn_manager: SVNManager):
        # self.svn_manager = svn_manager
        pass

    @staticmethod
    def run_test(request: pb2.TestUnit, context: ServicerContext) -> pb2.TestResult:
        """
        处理 F4_RunAndGetLog 的核心逻辑
        1. 输入参数校验,topox &conftest &test.py,
        2. 占用测试环境,simware,
            2.0 上传,输入参数,到svn服务器
            2.1 调用 DeployVirtualTool 接口,占用测试环境
        3. 运行代码,逐函数或整函数,
        4. 释放环境
        5. 填充返回结果

        ---适配现有流程,改成下面---
        1. 输入参数校验,topox &conftest &test.py,
        2. 调用,现成接口,运行&返回log
        3. 填充返回结果
        """
        # 1. 输入参数校验,topox &conftest &test.py,不能为空
        if not (request.network_setup and
                request.network_setup.topox and
                request.network_setup.conftest_py and
                request.script):
            # 设置 gRPC 状态码和详细信息
            context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
            context.set_details("输入参数存在空,请校验。")
            return pb2.TestResult(status="failed", log="输入数据校验失败")

        # 2. 调用,现成接口
        # 2.1 准备独立工作区
        try:
            local_path, svn_url = TestRunner.svn_manager.prepare_workspace()
        except Exception as e:
            context.set_code(grpc.StatusCode.RESOURCE_EXHAUSTED)
            context.set_details(f"Workspace preparation failed: {str(e)}")
            return pb2.TestResult(status="failed", log=f"Workspace setup failed: {str(e)}")

        # 2.2 保存测试文件,即aixcoder传入文件
        TestRunner._save_test_files(local_path, request)
        
        # 2.2.1 验证4个必需文件都已创建
        required_files = ["__init__.py", "conftest.py", "network.topox", "test_script.py"]
        missing_files = []
        for filename in required_files:
            if not os.path.exists(os.path.join(local_path, filename)):
                missing_files.append(filename)
        
        if missing_files:
            error_msg = f"缺少必需文件: {', '.join(missing_files)}"
            context.set_code(grpc.StatusCode.INTERNAL)
            context.set_details(error_msg)
            TestRunner.svn_manager.cleanup_workspace(local_path, svn_url)
            return pb2.TestResult(status="failed", log=error_msg)
        
        # 2.3 提交到SVN
        TestRunner.svn_manager.commit_files(local_path, "Test run files")

        # 2.4 调用脚本验证&修复接口
        validator = ScriptValidator()
        success, message, response_data = validator.validate_and_repair_scripts(svn_url)

        # 3. 填充返回结果
        try:
            if success:
                # 验证成功
                status = "passed"
                log_content = f"脚本验证成功: {message}"
            else:
                # 验证失败
                status = "failed"
                return_code = response_data.get('return_code', 'unknown')
                
                # 根据不同的返回码提供详细信息
                if return_code == "200" and "脚本验证失败" in message:
                    # 脚本验证失败，返回详细错误信息
                    log_content = f"脚本验证失败: {message}"
                elif return_code == "400":
                    log_content = f"请求参数错误: {message}"
                elif return_code == "500":
                    log_content = f"服务器内部错误: {message}"
                else:
                    log_content = f"验证失败 (返回码: {return_code}): {message}"
            
            return pb2.TestResult(
                status=status,
                log=log_content
            )
        finally:
            # 确保清理工作区
            TestRunner.svn_manager.cleanup_workspace(local_path, svn_url)

    @staticmethod
    def _save_test_files(local_path: str, request: pb2.TestUnit):
        """
        将所有测试文件保存到工作区
        确保创建以下4个必需文件：
        1. __init__.py (可以为空)
        2. conftest.py
        3. xxx.topox
        4. test_xx.py
        """
        try:
            # 1. 创建 __init__.py 文件 (可以为空)
            with open(os.path.join(local_path, "__init__.py"), "w", encoding='utf-8') as f:
                f.write("")  # 空文件
            
            # 2. 保存 conftest.py
            with open(os.path.join(local_path, "conftest.py"), "w", encoding='utf-8') as f:
                f.write(request.network_setup.conftest_py)

            # 3. 保存 topox 文件（使用 xxx.topox 格式命名）
            with open(os.path.join(local_path, "network.topox"), "w", encoding='utf-8') as f:
                f.write(request.network_setup.topox)

            # 4. 保存测试脚本（使用 test_xx.py 格式命名）
            with open(os.path.join(local_path, "test_script.py"), "w", encoding='utf-8') as f:
                f.write(request.script)

        except Exception as e:
            raise RuntimeError(f"Failed to save test files: {str(e)}")
